package com.lemon.core.common.web.http;

import com.lemon.core.common.lang.util.StreamUtils;
import com.lemon.core.common.lang.util.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.CookieStore;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author lottery
 * @ClassName HttpClient
 * @desc http client工具类
 * @Date 2018/11/2 15:28
 * @Version 1.0
 **/
public class HttpClient {

    public interface Constants {
        String CHARSET = "UTF-8";
        String X_PROTOBUF = "application/x-protobuf";
        String OCTET_STREAM_CONTENT_TYPE = "application/octet-stream";
        String X_JSON = "application/json";
        String URLENCODED = "application/x-www-form-urlencoded";
    }

    private static RequestConfig defaultRequestConfig;

    private static CloseableHttpClient closeableHttpClient;

    private static HttpClient httpClient;

    /**
     * 建立http连接的超时时间
     */
    private static Integer connectTimeout = 20000;

    /**
     * 从连接池获取连接的超时间
     */
    private static Integer connectRequestTimeout = 6000;

    /**
     * socket读取的超时时间（0为无限）
     */
    private static Integer socketTimeout = 20000;

    /**
     * 池中最大连接数
     */
    private static Integer maxTotal = 1000;

    /**
     * HttpClient中每个远程host最大连接数，一个host可能有多个连接
     */
    private static Integer maxPerRoute = 200;

    /**
     * 获得HttpClient单例
     * @return HttpClient
     */
    public static HttpClient getInstance() {
        if (httpClient == null) {
            synchronized (HttpClient.class) {
                if (httpClient == null) {
                    httpClient = new HttpClient();
                }
            }
        }
        return httpClient;
    }

    /**
     * 获取带有cokkie的HttpClient单例
     * @param jsessionId
     * @param domain
     * @param path
     * @return
     */
    public static HttpClient getInstance(String jsessionId, String domain, String path) {
        if (httpClient == null) {
            synchronized (HttpClient.class) {
                if (httpClient == null) {
                    httpClient = new HttpClient(jsessionId, domain, path);
                }
            }
        }
        return httpClient;
    }

    private HttpClient() {
        HttpClientBuilder clientBuilder = createHttpClientBuilder();
        closeableHttpClient = clientBuilder.build();
    }

    private HttpClient(String jsessionId, String domain, String path) {
        HttpClientBuilder clientBuilder = createHttpClientBuilder();

        // 新建一个Cookie
        CookieStore cookieStore = new BasicCookieStore();
        BasicClientCookie cookie = new BasicClientCookie("JSESSIONID", jsessionId);
        cookie.setVersion(0);
        cookie.setDomain(domain);
        cookie.setPath(path);
        cookieStore.addCookie(cookie);

        closeableHttpClient = clientBuilder.setDefaultCookieStore(cookieStore).build();
    }

    private HttpClientBuilder createHttpClientBuilder() {
        PoolingHttpClientConnectionManager poolingManager = new PoolingHttpClientConnectionManager();
        defaultRequestConfig = RequestConfig.custom()
                .setConnectTimeout(connectTimeout)
                .setConnectionRequestTimeout(connectRequestTimeout)
                .setSocketTimeout(socketTimeout)
                .build();

        poolingManager.setMaxTotal(maxTotal);
        poolingManager.setDefaultMaxPerRoute(maxPerRoute);

        return HttpClients.custom().setConnectionManager(poolingManager);
    }

    /**
     * 执行一个HttPost请求
     * @param url        请求地址
     * @param parameters 请求参数
     * @return response正确返回后的字符串
     */
    public String doPost(String url, Map<String, String> parameters) {
        HttpPost httpRequestBase = new HttpPost(url);
        httpRequestBase.setHeader("Content-Type", Constants.URLENCODED);
        if (parameters != null && !parameters.isEmpty()) {
            try {
                httpRequestBase.setEntity(new UrlEncodedFormEntity(
                        toNameValuePairs(parameters), Constants.CHARSET));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        return this.executeMethod(httpRequestBase);
    }

    /**
     * 执行一个HttPost请求
     * @param url        请求地址
     * @param parameters 请求参数
     * @param headers    请求头
     * @return response string
     */
    public String doPost(String url, Map<String, String> parameters, Map<String, String> headers) {
        HttpPost httpRequestBase = new HttpPost(url);
        httpRequestBase.setHeader("Content-Type", Constants.URLENCODED);
        if (headers != null && headers.size() > 0) {
            for (String headerName : headers.keySet()) {
                httpRequestBase.setHeader(headerName, headers.get(headerName));
            }
        }

        if (parameters != null && !parameters.isEmpty()) {
            try {
                httpRequestBase.setEntity(new UrlEncodedFormEntity(
                        toNameValuePairs(parameters), Constants.CHARSET));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        return this.executeMethod(httpRequestBase);
    }

    /**
     * 执行一个HttpPost请求
     * @param url           请求地址
     * @param jsonContent   请求参数JSON
     * @return
     */
    public String doJsonPost(String url, String jsonContent) {
        HttpPost httpRequestBase = new HttpPost(url);
        httpRequestBase.setHeader("Content-Type", "application/json;charset=UTF-8");
        StringEntity strEntity = new StringEntity(jsonContent, Constants.CHARSET);
        httpRequestBase.setEntity(strEntity);
        return this.executeMethod(httpRequestBase);
    }

    public String doXmlPost(String url, String xmlContent) {
        HttpPost httpRequestBase = new HttpPost(url);
        httpRequestBase.setHeader("Content-Type", "text/xml;charset=UTF-8");
        StringEntity strEntity = new StringEntity(xmlContent, Constants.CHARSET);
        httpRequestBase.setEntity(strEntity);
        return this.executeMethod(httpRequestBase);
    }

    public InputStream doJsonPostStream(String url, String jsonContent) {
        HttpPost httpRequestBase = new HttpPost(url);
        httpRequestBase.setHeader("Content-Type", "application/json;charset=UTF-8");
        StringEntity strEntity = new StringEntity(jsonContent, Constants.CHARSET);
        httpRequestBase.setEntity(strEntity);
        CloseableHttpResponse response = null;
        setDefaultRequestConfig(httpRequestBase);
        try {
            response = closeableHttpClient.execute(httpRequestBase);
            HttpEntity entity = response.getEntity();
            return StreamUtils.nonClosing(entity.getContent());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 执行一个HttpGet方法
     * @param url 请求地址
     * @return response 正确返回后的字符串
     */
    public String doGet(String url) {
        HttpGet get = new HttpGet(url);
        return this.executeMethod(get);
    }

    /**
     * 执行一个简单的http请求并获得字符串类型的返回数据
     * @param requestBase 请求对象
     * @return 如果返回200，那么返回请求的响应字符串数据
     */
    private String executeMethod(HttpRequestBase requestBase) {
        CloseableHttpResponse response = null;
        String result = StringUtils.EMPTY;
        setDefaultRequestConfig(requestBase);
        try {
            response = closeableHttpClient.execute(requestBase);
            HttpEntity entity = response.getEntity();
            if (entity == null || entity.getContent() == null) {
                return "";
            }
            result = inputStream2String(entity.getContent());

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (response != null) {
                try {
                    EntityUtils.consume(response.getEntity());
                    response.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return result;
    }

    /**
     * 设置默认的配置
     *
     * @param requestBase -
     */
    private void setDefaultRequestConfig(HttpRequestBase requestBase) {
        RequestConfig config = requestBase.getConfig();
        if (config == null) {
            requestBase.setConfig(defaultRequestConfig);
            return;
        }
        RequestConfig.Builder builder = RequestConfig.custom();
        if (config.getConnectionRequestTimeout() == -1) {
            builder.setConnectionRequestTimeout(defaultRequestConfig.getConnectionRequestTimeout());
        }
        if (config.getConnectTimeout() == -1) {
            builder.setConnectTimeout(defaultRequestConfig.getConnectTimeout());
        }
        if (config.getSocketTimeout() == -1) {
            builder.setSocketTimeout(defaultRequestConfig.getSocketTimeout());
        }

        config = builder
                .setExpectContinueEnabled(config.isExpectContinueEnabled())
                .setAuthenticationEnabled(config.isAuthenticationEnabled())
                .setRedirectsEnabled(config.isRedirectsEnabled())
                .setRelativeRedirectsAllowed(config.isRelativeRedirectsAllowed())
                .setCircularRedirectsAllowed(config.isCircularRedirectsAllowed())
                .setMaxRedirects(config.getMaxRedirects())
                .setCookieSpec(config.getCookieSpec())
                .setLocalAddress(config.getLocalAddress())
                .setProxy(config.getProxy())
                .setTargetPreferredAuthSchemes(config.getTargetPreferredAuthSchemes())
                .setProxyPreferredAuthSchemes(config.getProxyPreferredAuthSchemes()).build();

        requestBase.setConfig(config);
    }

    /**
     * 将输入流转化为字符串数据
     *
     * @param in 输入流
     * @return 将输入流中的内容转化为字符串数据
     */
    private String inputStream2String(InputStream in) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(1024 * 256);
        byte[] temp = new byte[1024 * 256];
        int i;
        while ((i = in.read(temp)) != -1) {
            byteArrayOutputStream.write(temp, 0, i);
        }
        return new String(byteArrayOutputStream.toByteArray(), Constants.CHARSET);
    }

    /**
     * 将map类型转化为请求的键值对
     *
     * @param params 需要发出的请求参数map
     * @return 返回对应的http请求参数键值对
     */
    private List<NameValuePair> toNameValuePairs(Map<String, String> params) {
        ArrayList<NameValuePair> list = new ArrayList<NameValuePair>();
        Set<Map.Entry<String, String>> entries = params.entrySet();
        for (Map.Entry<String, String> entry : entries) {
            NameValuePair nameValuePair = new BasicNameValuePair(entry.getKey(), entry.getValue());
            list.add(nameValuePair);
        }
        return list;
    }

    /**
     * 将map类型转化为请求的键值对，Object值类型
     *
     * @param params 需要发出的请求参数键值对
     * @return 返回对应的http请求键值对
     */
    private List<NameValuePair> toNameValuePairsObject(Map<String, Object> params) {

        ArrayList<NameValuePair> list = new ArrayList<NameValuePair>();
        Set<Map.Entry<String, Object>> entries = params.entrySet();
        for (Map.Entry<String, Object> entry : entries) {

            NameValuePair nameValuePair = new BasicNameValuePair(entry.getKey(), (String) entry.getValue());
            list.add(nameValuePair);
        }
        return list;
    }
}
